﻿using GoogleSpeechRecognition;
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;

namespace WordCloudInterface
{
    public class                                    MicrophoneRecorder : IDisposable
    {
        #region Attributes

        //
        // Constants
        //
        private readonly double                     SPEECH_RECOGNITION_CONFIDENCE;
        private readonly int                        CHUNK_SIZE;
        private readonly int                        RATE;
        private readonly int                        BITS;
        private const string                        TMP_FILE_1 = "stream_tmp1.wav";
        private const string                        TMP_FILE_2 = "stream_tmp2.wav";

        //
        // Parent form
        //
        private Interface                           parentForm;

        //
        // Attributes
        //
        private bool                                isSecondTempFile = false;
        private WaveIn                              sourceStream = null;
        private WaveFileWriter                      waveWriter = null;
        private List<string>                        responses = null;

        #endregion

        #region Constructor/Destructor

        //
        // Constructor
        //
        public                                      MicrophoneRecorder(Interface ParentForm,
                                                                        double SpeechRecognitionConfidence = 0.0,
                                                                        int ChunkSize = 800000,
                                                                        int Rate = 16000,
                                                                        int Bits = 16)
        {
            this.parentForm = ParentForm;
            this.CHUNK_SIZE = ChunkSize;
            this.SPEECH_RECOGNITION_CONFIDENCE = SpeechRecognitionConfidence;
            this.RATE = Rate;
            this.BITS = Bits;
            this.isSecondTempFile = false;
        }

        //
        // Destructor
        //
        public void                                 Dispose()
        {
            this.StopRecording();
        }

        #endregion

        #region Private Methods

        //
        // Method called automatically each time data from microphone is available.
        //
        private void                                MicrophoneDataAvailable(object sender, NAudio.Wave.WaveInEventArgs e)
        {
            if (waveWriter == null)
                return;

            waveWriter.Write(e.Buffer, 0, e.BytesRecorded);
            waveWriter.Flush();

            if (waveWriter.Length > CHUNK_SIZE) // Update word cloud every CHUNK_SIZE.
            {
                StopWaveWriter(true);
                this.parentForm.FromMicrophone_WordCloudUpdate(this.responses);
            }
        }

        //
        // Recognize content from a wav file and add it to the list.
        //
        private void                                AddNewResponses(string filename)
        {
            var                                     stt = new SpeechToText(this.parentForm.Language);
            var                                     response = stt.Recognize(filename, false, true);

            foreach (TextResponse txt in response)
            {
                if (txt.Confidence >= SPEECH_RECOGNITION_CONFIDENCE)
                    this.responses.Add(txt.Utterance + " ");
            }
        }

        //
        // Stop wave writer after getting last data in buffer and optionnaly starts a new writer.
        //
        private void                                StopWaveWriter(bool SwitchToNewWaveWriter = false)
        {
            if (waveWriter != null)
            {
                waveWriter.Dispose();
                waveWriter.Close();
                if (!this.isSecondTempFile)
                {
                    if (SwitchToNewWaveWriter == true)
                    {
                        waveWriter = new NAudio.Wave.WaveFileWriter(TMP_FILE_2, sourceStream.WaveFormat);
                        this.isSecondTempFile = true;
                    }
                    else
                        waveWriter = null;
                    this.AddNewResponses(TMP_FILE_1);
                }
                else
                {
                    if (SwitchToNewWaveWriter == true)
                    {
                        waveWriter = new NAudio.Wave.WaveFileWriter(TMP_FILE_1, sourceStream.WaveFormat);
                        this.isSecondTempFile = false;
                    }
                    else
                        waveWriter = null;
                    this.AddNewResponses(TMP_FILE_2);
                }
            }
        }

        //
        // Delete the temporary files and the stored recognized audio content.
        //
        private void                                CleanTempFilesAndResponses()
        {
            // Clean temp files
            if (File.Exists(TMP_FILE_1))
                File.Delete(TMP_FILE_1);
            if (File.Exists(TMP_FILE_2))
                File.Delete(TMP_FILE_2);

            // Clean responses
            if (this.responses != null)
            {
                this.responses.Clear();
                this.responses = null;
            }
        }

        #endregion

        #region Public Methods

        //
        // Start recording from the microphone while updating the word cloud every time there's a new audio chunk.
        //
        public void                                 StartRecording(int deviceNumber)
        {
            // We return if already in recording mode
            if (this.responses != null)
                return;

            // Init attributes
            this.isSecondTempFile = false;
            this.responses = new List<string>();

            // Init microphone
            sourceStream = new NAudio.Wave.WaveIn();
            sourceStream.DeviceNumber = deviceNumber;
            sourceStream.WaveFormat = new NAudio.Wave.WaveFormat(RATE, BITS, NAudio.Wave.WaveIn.GetCapabilities(deviceNumber).Channels);
            sourceStream.DataAvailable += new EventHandler<NAudio.Wave.WaveInEventArgs>(MicrophoneDataAvailable);

            // Init wave writer
            waveWriter = new NAudio.Wave.WaveFileWriter(TMP_FILE_1, sourceStream.WaveFormat);

            // Start microphone recording
            sourceStream.StartRecording();
        }

        //
        // Stop recording from the microphone and generate the final word cloud.
        //
        public void                                 StopRecording()
        {
            // Stop microphone
            if (sourceStream != null)
            {
                sourceStream.StopRecording();
                sourceStream.Dispose();
                sourceStream = null;
            }

            // Stop wave writer after getting last data in buffer
            this.StopWaveWriter();

            // Update word cloud
            this.parentForm.FromMicrophone_WordCloudUpdate(this.responses);

            // Clean data, prepare to next recording
            this.CleanTempFilesAndResponses();
        }

        #endregion

        #region Public Static Methods

        //
        // Retrieve a list of available microphones.
        //
        public static List<Tuple<string, string>>   GetSources()
        {
            List<Tuple<string, string>> result = new List<Tuple<string, string>>();
            List<WaveInCapabilities> sources = new List<WaveInCapabilities>();

            for (int i = 0; i < WaveIn.DeviceCount; i++)
            {
                sources.Add(WaveIn.GetCapabilities(i));
            }
            foreach (var source in sources)
            {
                result.Add(new Tuple<string, string>(source.ProductName, source.Channels.ToString()));
            }
            return (result);
        }

        #endregion
    }
}
